package org.unit.testmgmt.generator.wizard;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;

import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Control;
import org.unit.testmgmt.generator.GeneratorUtils;
import org.unit.testmgmt.messages.Messages;
import org.unit.testmgmt.tml.Constructor;
import org.unit.testmgmt.tml.Method;
import org.unit.testmgmt.tml.Mocks;
import org.unit.testmgmt.tml.Param;
import org.unit.testmgmt.tml.Result;
import org.unit.testmgmt.tml.Test;
import org.unit.testmgmt.tml.TestBase;
import org.unit.testmgmt.tml.TestBases;
import org.unit.testmgmt.tml.UTMModel;
import org.unit.testmgmt.ui.generator.wizard.GeneratorWizardBasePage;
import org.unit.testmgmt.ui.generator.wizard.GeneratorWizardTestBasesPage;
import org.unit.testmgmt.ui.table.ComboboxEntry;
import org.unit.testmgmt.ui.table.ITableEntry;
import org.unit.testmgmt.ui.table.ITableEntryListListener;
import org.unit.testmgmt.ui.table.TableEntryGeneric;
import org.unit.testmgmt.ui.table.TableEntryList;
import org.unit.testmgmt.ui.table.TableViewerBase;
import org.unit.testmgmt.ui.table.TableViewerColumn;
import org.unit.testmgmt.ui.table.TableViewerColumn.Columntype;
import org.unit.testmgmt.ui.table.TableViewerColumns;

/**
 * The controller for the test bases page.
 * 
 * @author Robert Streng
 * 
 */
public class GeneratorWizardTestBases extends GeneratorWizardBase {

	private Vector<IMethod> baseclassConstructors;
	private Vector<IMethod> baseclassMethods;

	private TableViewerColumn colTestbaseId = new TableViewerColumn(
			Messages.GeneratorWizardTestBases_testBaseId, SWT.LEFT, Columntype.STRING);

	private TableViewerColumn colMockMethod = null;
	private TableViewerColumn colMockMethodReturn = new TableViewerColumn(
			Messages.GeneratorWizardTestBases_ReturnValue, SWT.LEFT, Columntype.STRING);

	private HashMap<IMethod, TableEntryList<TableEntryGeneric>> testbasesMap = new HashMap<IMethod, TableEntryList<TableEntryGeneric>>();
	private HashMap<TableEntryGeneric, TableEntryList<TableEntryGeneric>> mocksMap = new HashMap<TableEntryGeneric, TableEntryList<TableEntryGeneric>>();

	private TableViewerBase tblTestbases;
	private TableViewerBase tblMocks;
	private boolean testbaseIdUnique = false;
	private boolean initialized = false;

	public GeneratorWizardTestBases(UTMModel model, GeneratorWizardBasePage generatorWizardPageBase) {
		super(model, generatorWizardPageBase);

		baseclassConstructors = model.getUtmElements().getConstructorsAndMethods()
				.getBaseClassConstructors();
		baseclassMethods = model.getUtmElements().getConstructorsAndMethods().getBaseClassMethods();

		colMockMethodReturn.setWidth(150);
	}

	@Override
	protected GeneratorWizardTestBasesPage getPage() {
		return (GeneratorWizardTestBasesPage) super.getPage();
	}

	@Override
	public void initPage() {
		GeneratorWizardTestBasesPage page = getPage();
		Test tmlTest = getModel().getTmlTest();

		initCboConstructor(page, tmlTest);

		addListener(page);

		checkStatus();

		initialized = true;
	}

	/**
	 * Initializes the drop down for the constructors.
	 * 
	 * @param page
	 * @param tmlTest
	 */
	private void initCboConstructor(GeneratorWizardTestBasesPage page, Test tmlTest) {
		List<IMethod> usedConstructors = new ArrayList<IMethod>();

		// initialize from TML
		IMethod availableConstructor;
		if (tmlTest != null) {
			TestBases tmlTestbases = tmlTest.getTestBases();
			if (tmlTestbases != null) {
				for (Constructor tmlConstructor : tmlTestbases.getConstructor()) {

					// default-constructor
					if (baseclassConstructors.size() == 0) {
						initTblTestbase(tmlConstructor, null);
						continue;
					}

					availableConstructor = getAvailableConstructor(tmlConstructor);

					if (availableConstructor != null) {
						page.getCboConstructor().add(availableConstructor);
						initTblTestbase(tmlConstructor, availableConstructor);
						usedConstructors.add(availableConstructor);
					}

				}
			}
		}

		// initialize from base-class
		if (baseclassConstructors.size() > 0) {
			for (IMethod method : baseclassConstructors) {
				if (!usedConstructors.contains(method)) {
					page.getCboConstructor().add(method);
				}
			}
		}
		else {
			// implicit default constructor
			page.getCboConstructor().add(
					getModel().getUtmElements().getClassesAndPackages().getTestBase()
							.getElementName().replace(".java", "") //$NON-NLS-1$
							+ "()"); //$NON-NLS-1$
		}
	}

	/**
	 * Returns the available constructors.
	 * 
	 * @param tmlConstructor
	 * @return the available constructors
	 */
	private IMethod getAvailableConstructor(Constructor tmlConstructor) {
		List<Param> tmlParams = tmlConstructor.getParam();
		ILocalVariable[] parameters;
		boolean constructorFound = false;
		String paramType;
		IMethod closestConstructor = null;

		for (IMethod constructor : baseclassConstructors) {
			if (closestConstructor == null) {
				closestConstructor = constructor;
			}

			try {
				parameters = constructor.getParameters();
			}
			catch (JavaModelException e) {
				throw new RuntimeException(
						Messages.GeneratorWizardTestBases_ErrorDuringConstructorHandling);
			}

			if (tmlParams.size() == parameters.length) {
				constructorFound = true;
				closestConstructor = constructor;

				for (int i = 0; i < tmlParams.size(); i++) {
					paramType = Signature.getSignatureSimpleName(parameters[i].getTypeSignature());
					if (!tmlParams.get(i).getType().equals(paramType)) {
						constructorFound = false;
						break;
					}
					closestConstructor = constructor;
				}

				if (constructorFound) {
					return constructor;
				}
			}
		}

		return closestConstructor;
	}

	/**
	 * Adds the listener to the pages.
	 * 
	 * @param page
	 */
	private void addListener(GeneratorWizardTestBasesPage page) {
		page.getCboConstructor().addSelectionChangedListener(new ISelectionChangedListener() {

			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				IStructuredSelection selection = (IStructuredSelection) event.getSelection();
				Object selectionObj = selection.getFirstElement();

				if (selectionObj instanceof IMethod)
					handleConstructorSelection((IMethod) selectionObj);
				else
					handleConstructorSelection(null);

			}
		});
	}

	/**
	 * Checks the status of the page.
	 */
	private void checkStatus() {
		testbaseIdUnique = false;

		// check if test-base-IDs are unique
		HashSet<String> testbaseIds = new HashSet<String>();

		for (TableEntryList<TableEntryGeneric> testbasesList : testbasesMap.values()) {
			for (TableEntryGeneric tblEntry : testbasesList.getTableEntries()) {
				String testbaseId = (String) tblEntry.getValue(colTestbaseId);

				if (!("".equals(testbaseId) || testbaseId.startsWith("[") || testbaseId //$NON-NLS-1$
						.endsWith("]"))) { //$NON-NLS-1$
					if (testbaseIds.contains(testbaseId)) {
						getPage().updateStatus(
								Messages.GeneratorWizardTestBases_TestBaseIdMustBeUnique);
						return;
					}
					else {
						testbaseIds.add(testbaseId);
					}
				}
				else {
					getPage().updateStatus(Messages.GeneratorWizardTestBases_MustEditTestIds);
					return;
				}
			}
		}

		testbaseIdUnique = true;
		getPage().updateStatus(null);
	}

	/**
	 * Returns if the test base id is unique.
	 * 
	 * @return true if the test base id is unique
	 */
	public boolean isTestbaseIdUnique() {
		return testbaseIdUnique;
	}

	@SuppressWarnings("unchecked")
	private void createTblTestbases(IMethod selectedConstructor) {
		// delete old elements
		for (Control control : getPage().getGrpTblTestbases().getChildren())
			control.dispose();
		for (Control control : getPage().getGrpTblMocks().getChildren())
			control.dispose();

		TableViewerColumns tblColumns = createTblTestbaseColumns(selectedConstructor);

		TableEntryList<TableEntryGeneric> tblEntryList;

		if ((tblEntryList = testbasesMap.get(selectedConstructor)) == null) {
			tblEntryList = new TableEntryList<TableEntryGeneric>(TableEntryGeneric.class,
					tblColumns);
		}

		tblTestbases = new TableViewerBase(getPage().getGrpTblTestbases(), tblColumns, tblEntryList);

		// layout
		getPage().layoutTblTestbases(tblTestbases);

		// save test-bases
		if (!testbasesMap.containsKey(selectedConstructor)) {
			testbasesMap.put(selectedConstructor,
					(TableEntryList<TableEntryGeneric>) tblTestbases.getTableEntryList());
		}

		// add listener
		tblTestbases.getTable().addSelectionListener(new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				IStructuredSelection selection = (IStructuredSelection) tblTestbases.getSelection();
				handleTestBaseSelection((TableEntryGeneric) selection.getFirstElement());
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// nothing
			}
		});

		tblTestbases.getTableEntryList().addChangeListener(new ITableEntryListListener() {
			@Override
			public void updateTableEntry(ITableEntry tblEntry, TableViewerColumn column,
					Object value, Object oldValue) {
				checkStatus();
			}

			@Override
			public void removeTableEntry(ITableEntry tblEntry) {
				checkStatus();
			}

			@Override
			public void addTableEntry(ITableEntry tblEntry) {
				checkStatus();
			}
		});

		// select first entry
		if (initialized) {
			Object tblEntry = tblTestbases.getTableViewer().getElementAt(0);
			if (tblEntry instanceof TableEntryGeneric) {
				handleTestBaseSelection((TableEntryGeneric) tblEntry);
			}
		}
	}

	/**
	 * Creates the columns of the table test bases.
	 * 
	 * @param selectedConstructor
	 * @return the created columns of the table bases
	 */
	private TableViewerColumns createTblTestbaseColumns(IMethod selectedConstructor) {
		TableViewerColumns tblColumns = new TableViewerColumns();

		// standard columns
		tblColumns.addColumn(colTestbaseId);

		// constructor initializer
		if (selectedConstructor != null) {

			String typeSignature;
			try {
				for (ILocalVariable param : selectedConstructor.getParameters()) {
					typeSignature = param.getTypeSignature();
					tblColumns.addColumn(TableViewerColumns.createColumn(param.getElementName(),
							Signature.getSignatureSimpleName(typeSignature)));
				}
			}
			catch (JavaModelException e) {
				throw new RuntimeException(
						Messages.GeneratorWizardTestBases_ErrorWhileGetParamsFromSelectedMethod);
			}
		}

		return tblColumns;
	}

	/**
	 * Initializes the table test bases. 
	 * 
	 * @param tmlConstructor
	 * @param availableConstructor
	 */
	private void initTblTestbase(Constructor tmlConstructor, IMethod availableConstructor) {
		// create table-columns and model
		TableViewerColumns tblTestbaseColumns = createTblTestbaseColumns(availableConstructor);
		TableEntryList<TableEntryGeneric> tblTestbasesEntryList = new TableEntryList<TableEntryGeneric>(
				TableEntryGeneric.class, tblTestbaseColumns);
		TableEntryGeneric tblTestbasesEntry;

		for (TestBase tmlTestbase : tmlConstructor.getTestBase()) {
			tblTestbasesEntry = tblTestbasesEntryList.createTableEntry();

			// test-base-id
			tblTestbasesEntry.setValue(tblTestbaseColumns.getColumn(0), tmlTestbase.getName());

			// parameters
			int i = 0;
			for (String paramValue : tmlTestbase.getParamValue()) {
				tblTestbasesEntry.setValue(tblTestbaseColumns.getColumn(i + 1), paramValue);
				i++;
			}

			// mocks
			initTblMocks(tmlTestbase.getMocks(), tblTestbasesEntry);
		}

		testbasesMap.put(availableConstructor, tblTestbasesEntryList);
	}

	/**
	 * Initializes the table mocks.
	 * 
	 * @param tmlMocks
	 * @param tblTestbasesEntry
	 */
	private void initTblMocks(Mocks tmlMocks, TableEntryGeneric tblTestbasesEntry) {
		if (tmlMocks == null)
			return;

		// create table-columns and model
		TableViewerColumns tblMocksColumns = createTblMocksColumns();
		TableEntryList<TableEntryGeneric> tblMocksEntryList = new TableEntryList<TableEntryGeneric>(
				TableEntryGeneric.class, tblMocksColumns);
		TableEntryGeneric tblMocksEntry;

		IMethod value;
		for (Method tmlMethod : tmlMocks.getMethod()) {
			tblMocksEntry = tblMocksEntryList.createTableEntry();

			for (ComboboxEntry cboEntry : colMockMethod.getComboboxEntries()) {
				value = (IMethod) cboEntry.getValue();
				if (GeneratorUtils.compareMethods(value, tmlMethod)) {
					tblMocksEntry.setValue(colMockMethod, cboEntry);
					break;
				}
			}

			// mock-method return
			if (tmlMethod.getResult() != null)
				tblMocksEntry.setValue(colMockMethodReturn, tmlMethod.getResult().getValue());
		}

		mocksMap.put(tblTestbasesEntry, tblMocksEntryList);
	}

	/**
	 * Handle the test base selection.
	 * 
	 * @param selectedTestbase
	 */
	protected void handleTestBaseSelection(TableEntryGeneric selectedTestbase) {
		createTblMocks(selectedTestbase);
	}

	/**
	 * Handle the constructor selection.
	 * 
	 * @param selectedConstructor
	 */
	protected void handleConstructorSelection(IMethod selectedConstructor) {
		createTblTestbases(selectedConstructor);
	}

	/**
	 * Creates the table mocks.
	 * 
	 * @param selectedTestbase
	 */
	@SuppressWarnings("unchecked")
	private void createTblMocks(TableEntryGeneric selectedTestbase) {
		// delete old elements
		for (Control control : getPage().getGrpTblMocks().getChildren())
			control.dispose();

		TableViewerColumns tblColumns = createTblMocksColumns();

		TableEntryList<TableEntryGeneric> tblEntryList;
		if ((tblEntryList = mocksMap.get(selectedTestbase)) == null) {
			tblEntryList = new TableEntryList<TableEntryGeneric>(TableEntryGeneric.class,
					tblColumns);
		}

		tblMocks = new TableViewerBase(getPage().getGrpTblMocks(), tblColumns, tblEntryList);

		// layout
		getPage().layoutTblMocks(tblMocks);

		// save mocks
		if (!mocksMap.containsKey(selectedTestbase)) {
			mocksMap.put(selectedTestbase,
					(TableEntryList<TableEntryGeneric>) tblMocks.getTableEntryList());
		}
	}

	/**
	 * Creates the columns of the table mocks.
	 * 
	 * @return columns of the table mocks
	 */
	private TableViewerColumns createTblMocksColumns() {
		TableViewerColumns tblColumns = new TableViewerColumns();

		// standard columns
		tblColumns.addColumn(getColMockMethods());
		tblColumns.addColumn(colMockMethodReturn);

		return tblColumns;
	}

	@Override
	public void updateModel() {
		Test tmlTest = getModel().getTmlTest();

		TestBases tmlTestbases = tmlTest.getTestBases();
		tmlTestbases = getObjectFactory().createTestBases();
		tmlTest.setTestBases(tmlTestbases);

		List<Constructor> tmlConstructor = tmlTestbases.getConstructor();

		if (testbasesMap.size() > 0) {
			// delete old constructors
			tmlConstructor.clear();
		}
		else {
			// default-constructor
			IMethod constructor = null;
			if (baseclassConstructors.size() > 0) {
				constructor = baseclassConstructors.get(0);
			}
			updateModelConstructorBlock(tmlTestbases, constructor);
			return;
		}

		for (Entry<IMethod, TableEntryList<TableEntryGeneric>> testbasesEntry : testbasesMap
				.entrySet()) {
			try {
				updateModelTestBaseConstructors(tmlTestbases, testbasesEntry);
			}
			catch (JavaModelException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * Updates the test base constructors from the page.
	 * 
	 * @param tmlTestbases
	 * @param testbasesEntry
	 * @throws JavaModelException
	 */
	private void updateModelTestBaseConstructors(TestBases tmlTestbases,
			Entry<IMethod, TableEntryList<TableEntryGeneric>> testbasesEntry)
			throws JavaModelException {

		Constructor constructorNode;
		String testBaseId;
		IMethod constructor;
		TestBase testBaseNode;

		constructor = testbasesEntry.getKey();

		// create constructor-block
		constructorNode = updateModelConstructorBlock(tmlTestbases, constructor);

		for (TableEntryGeneric tblEntryTestBase : testbasesEntry.getValue().getTableEntries()) {

			testBaseId = (String) tblEntryTestBase.getValue(colTestbaseId);
			if ((testBaseId.startsWith("[") && testBaseId.endsWith("]"))) //$NON-NLS-1$ //$NON-NLS-2$
				continue;

			testBaseNode = getObjectFactory().createTestBase();
			constructorNode.getTestBase().add(testBaseNode);

			// create test-base-block
			updateModelTestBaseBlock(testBaseNode, tblEntryTestBase, testBaseId,
					constructorNode.getParam());

			// create mocks-block
			updateModelTestBaseMocks(testBaseNode, tblEntryTestBase);
		}

	}

	/**
	 * Updates the constructor block.
	 * 
	 * @param tmlTestbases
	 * @param constructor
	 * @return TML-Constructor
	 */
	private Constructor updateModelConstructorBlock(TestBases tmlTestbases, IMethod constructor) {
		Constructor constructorNode;
		constructorNode = getObjectFactory().createConstructor();
		tmlTestbases.getConstructor().add(constructorNode);
		Param paramsNode = null;

		if (constructor != null) {
			try {
				for (ILocalVariable param : constructor.getParameters()) {
					paramsNode = getObjectFactory().createParam();
					constructorNode.getParam().add(paramsNode);
					paramsNode.setName(param.getElementName());
					paramsNode.setType(Signature.getSignatureSimpleName(param.getTypeSignature()));
				}
			}
			catch (JavaModelException e) {
				throw new RuntimeException(
						Messages.GeneratorWizardTestBases_ErrorWhileWritingTmlFile);
			}
		}

		return constructorNode;
	}

	/**
	 * Updates the test test base block.
	 * 
	 * @param testbaseNode
	 * @param tblEntryTestbase
	 * @param testBaseId
	 * @param params
	 */
	private void updateModelTestBaseBlock(TestBase testbaseNode,
			TableEntryGeneric tblEntryTestbase, String testBaseId, List<Param> params) {
		int colIndex = 0;

		testbaseNode.setName(testBaseId);

		for (Object value : tblEntryTestbase.getValues()) {

			if (colIndex++ == 0) {
				continue; // test-base-id-column
			}
			else {
				// parameters
				testbaseNode.getParamValue().add(value.toString());
			}
		}

	}

	/**
	 * Updates the test base mocks from the page.
	 * 
	 * @param testbaseNode
	 * @param tblEntryTestbase
	 * @throws JavaModelException
	 */
	private void updateModelTestBaseMocks(TestBase testbaseNode, TableEntryGeneric tblEntryTestbase)
			throws JavaModelException {
		TableEntryList<TableEntryGeneric> tblMockEntryList = mocksMap.get(tblEntryTestbase);

		if (tblMockEntryList != null) {

			Vector<TableEntryGeneric> tblMockEntries = tblMockEntryList.getTableEntries();

			if (tblMockEntries != null && tblMockEntries.size() > 0) {
				Mocks tmlMocks = getObjectFactory().createMocks();
				testbaseNode.setMocks(tmlMocks);

				for (TableEntryGeneric tblMockEntry : tblMockEntries) {
					Method tmlMethod = getObjectFactory().createMethod();
					tmlMocks.getMethod().add(tmlMethod);

					// method-name
					ComboboxEntry cboEntry = (ComboboxEntry) tblMockEntry.getValue(colMockMethod);

					Object value = cboEntry.getValue();

					if (value instanceof IMethod) {
						updateModelMethod((IMethod) value, tmlMethod);
					}
					else
						continue;

					Object returnValue = tblMockEntry.getValue(colMockMethodReturn);
					if (returnValue != null) {
						Result tmlResult = tmlMethod.getResult();

						if (tmlResult != null)
							tmlResult.setValue(returnValue.toString());
					}
				}
			}
		}
	}

	/**
	 * Updates the method.
	 * 
	 * @param method
	 * @param tmlMethod
	 * @throws JavaModelException
	 */
	private void updateModelMethod(IMethod method, Method tmlMethod) throws JavaModelException {
		GeneratorWizardMain controller = getPage().getMainPage().getController();
		controller.updateModelMethod(method, tmlMethod);
	}

	/**
	 * @return column mock methods
	 */
	private TableViewerColumn getColMockMethods() {

		if (colMockMethod == null) {
			JavaElementLabelProvider labelProvider = new JavaElementLabelProvider(
					JavaElementLabelProvider.SHOW_DEFAULT
							| JavaElementLabelProvider.SHOW_RETURN_TYPE
							| JavaElementLabelProvider.SHOW_TYPE);

			String methodLabel;
			IMethod methodObject;
			ComboboxEntry entry;
			Set<ComboboxEntry> comboboxEntries = new HashSet<ComboboxEntry>();

			for (int i = 0; i < baseclassMethods.size(); i++) {
				methodObject = baseclassMethods.get(i);
				methodLabel = labelProvider.getText(methodObject);

				entry = new ComboboxEntry();
				entry.setKey(i);
				entry.setLabel(methodLabel);
				entry.setValue(methodObject);

				comboboxEntries.add(entry);
			}

			colMockMethod = new TableViewerColumn(Messages.GeneratorWizardTestBases_method,
					SWT.LEFT, Columntype.STRING, comboboxEntries);
			colMockMethod.setWidth(300);
		}

		return colMockMethod;
	}

	/**
	 * Returns the column test base id.
	 * 
	 * @return the test base id column
	 */
	public TableViewerColumn getColTestbaseId() {
		return colTestbaseId;
	}

	/**
	 * Returns the test bases.
	 * 
	 * @return the test bases
	 */
	public HashMap<IMethod, TableEntryList<TableEntryGeneric>> getTestbases() {
		return testbasesMap;
	}

}
